对于应用程序来说，只有INTEGER这样的基本类型是不够的。要表示矩阵或复数等数学对象，必须在已有数据类型的基础上构造新的数据类型。这些新数据类型通常称为聚合(aggregate)或组合(composite)。

数组是由相同类型的元素组成的序列。在LLVM中，数组总是静态的，所以元素的数量恒定。tinylang类型ARRAY [10] OF INTEGER或C类型long[10]在IR中表示如下:

\begin{shell}
[10 x i64]
\end{shell}

结构是不同类型的组合物。在编程语言中，通常用命名成员表示。例如，在tinylang中，结构可写成RECORD x: REAL;  color: INTEGER;  y: REAL;  END; C语言中同样的结构是struct \{ float x; long color; float y; \};。在LLVM IR中，只列出类型名:

\begin{shell}
{ float, i64, float }
\end{shell}

要访问成员，需要使用数字索引。与数组一样，第一个元素的索引号为0。

该结构的成员按照数据布局字符串中的规范在内存中排列。有关LLVM中数据布局字符串的更多信息，详见第4章。

若有必要，还会插入未使用的填充字节。若需要控制内存布局，可以使用打包结构，其中所有元素都具有1字节对齐方式。在C语言中，可以以下方式利用结构体的\_\_packed\_\_属性:

\begin{cpp}
struct __attribute__((__packed__)) { float x; long long color; float y; }
\end{cpp}

LLVM IR中的语法略有不同:

\begin{shell}
<{ float, i64, float }>
\end{shell}

加载到寄存器中，数组和结构体视为一个单元。例如，不可能将数组值寄存器\%x的单个元素引用为\%x[3]。这是由于SSA形式，不能判断\%x[i]和\%x[j]是否指的是同一个元素。相反，需要特殊的指令来提取和插入单元素值到数组中。要读取第二个元素，需要使用以下指令:

\begin{shell}
%el2 = extractvalue [10 x i64] %x, 1
\end{shell}

也可以更新一个元素，比如第一个元素:

\begin{shell}
%xnew = insertvalue [10 x i64] %x, i64 %el2, 0
\end{shell}

这两个指令也适用于结构体。例如，使用寄存器\%pt访问color成员:

\begin{shell}
%color = extractvalue { float, float, i64 } %pt, 2
\end{shell}

这两条指令都有一个重要的限制:索引必须是一个常量。对于结构体来说，索引号只是名称的替代，而C等语言没有动态计算结构成员名称的概念。对于数组来说，只是无法高效地实现。这两条指令在元素数量较少，且已知的特定情况下都有价值。例如，复数可以建模为两个浮点数组成的数组。传递这个数组是合理的，而且很清楚在计算过程中必须访问数组的哪一部分。

对于前端的一般使用，必须借助于指向内存的指针。LLVM中的所有全局值都用指针表示。下面将全局变量@arr声明为包含8个i64元素的数组，这等价于C语言中的long arr[8]声明:

\begin{shell}
define i64 @second() {
    %1 = load i64, ptr getelementptr inbounds ([8 x i64], ptr @arr, i64
    0, i64 1)
    ret i64 %1
}
\end{shell}

getelementptr指令是地址计算的主力，需要更详细的解释。第一个操作数[8 x i64]是指令所操作的基类型，第二个操作数ptr @arr指定基指针。请注意这里的差别:声明了一个包含八个元素的数组，由于所有全局值都视为指针，所以有一个指向数组的指针。在C的语法中，实际上使用long (*arr)[8]!其结果是，在对元素进行索引之前，必须先对指针解引用，如C语言中的arr[0][1]。第三个操作数i64 0对指针进行解引用，第四个操作数i64 1是元素索引，计算的结果是索引元素的地址。还需要注意的是，此指令不涉及内存存取。

除了结构体，索引参数不需要是常量，可以在循环中使用getelementptr指令来检索数组的元素。这里对结构体的处理有所不同:只能使用常量，且类型必须为i32。

有了这些知识，数组可以很容易地集成到第4章的代码生成器中，要创建这个类型，必须扩展convertType()方法。若Arr变量保存数组的类型标识符，并且假设数组中的元素数量为整数字面值，则可以向convertType()方法添加以下代码来处理数组:

\begin{cpp}
if (auto *ArrayTy =
            llvm::dyn_cast<ArrayTypeDeclaration>(Ty)) {
    llvm::Type *Component =
        convertType(ArrayTy->getType());
    Expr *Nums = ArrayTy->getNums();
    uint64_t NumElements =
        llvm::cast<IntegerLiteral>(Nums)
            ->getValue()
            .getZExtValue();
    llvm::Type *T =
        llvm::ArrayType::get(Component, NumElements);
    // TypeCache is a mapping between the original
    // TypeDeclaration (Ty) and the current Type (T).
    return TypeCache[Ty] = T;
}
\end{cpp}

该类型可用于声明全局变量。对于局部变量，需要为数组分配内存。我们在过程的第一个基本块中可以这样做:

\begin{cpp}
for (auto *D : Proc->getDecls()) {
    if (auto *Var =
            llvm::dyn_cast<VariableDeclaration>(D)) {
        llvm::Type *Ty = mapType(Var);
        if (Ty->isAggregateType()) {
            llvm::Value *Val = Builder.CreateAlloca(Ty);
            // The following method requires a BasicBlock (Curr),
            // a VariableDeclation (Var), and an llvm::Value (Val)
            writeLocalVariable(Curr, Var, Val);
        }
    }
}
\end{cpp}

为了读写一个元素，必须生成getelementptr指令，这会添加到emitExpr()(读取值)和emitStmt()(写入值)方法中。要读取数组的元素，首先读取变量的值，再处理变量的选择器。对于每个索引，计算表达式并存储其值。基于这个列表，计算引用元素的地址并加载值:

\begin{cpp}
auto &Selectors = Var->getSelectors();
for (auto I = Selectors.begin(), E = Selectors.end();
        I != E; ) {
    if (auto *IdxSel =
            llvm::dyn_cast<IndexSelector>(*I)) {
        llvm::SmallVector<llvm::Value *, 4> IdxList;
        while (I != E) {
            if (auto *Sel =
                    llvm::dyn_cast<IndexSelector>(*I)) {
                IdxList.push_back(emitExpr(Sel->getIndex()));
                ++I;
            } else
                break;
        }
        Val = Builder.CreateInBoundsGEP(Val->getType(), Val, IdxList);
        Val = Builder.CreateLoad(
            Val->getType(), Val);
    }
    // . . . Check for additional selectors and handle
    // appropriately by generating getelementptr and load.
    else {
        llvm::report_fatal_error("Unsupported selector");
    }
}
\end{cpp}

写入数组元素使用相同的代码，但不生成加载指令，可以使用指针作为存储指令中的目标。对于记录，可以使用类似的方法。记录成员的选择器包含名为Idx的常量字段索引，可把这个常量转换成一个LLVM常量值:

\begin{cpp}
llvm::Value *FieldIdx = llvm::ConstantInt::get(Int32Ty, Idx);
\end{cpp}

然后，在Builder.CreateGEP()方法中使用value，就像在数组中一样。

现在，应该知道如何将聚合数据类型转换为LLVM IR。以符合系统的方式传递这些类型值要非常谨慎，下一节将介绍如何正确地实现。













